package cn.cloudtogo.server.mysql.service;

import cn.cloudtogo.common.Assert;
import cn.cloudtogo.common.GendbException;
import cn.cloudtogo.common.model.ExternalDatasourceModel;
import cn.cloudtogo.common.types.RequestHeaderObject;
import cn.cloudtogo.server.mysql.SpringJdbcTemplateContext;
import cn.cloudtogo.support.mysql.column.AlterColumnBuilder;
import cn.cloudtogo.support.mysql.column.ColumnApi;
import cn.cloudtogo.support.mysql.column.ColumnDefinition;
import cn.cloudtogo.support.mysql.table.TableApi;
import cn.cloudtogo.support.mysql.table.TableMetadata;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.cloudtogo.common.types.RequestHeaderObject.extractExternalDatasourceModel;
import static cn.cloudtogo.common.util.StringHelper.pretty;

/**
 * @author yaolianhua789@gmail.com
 **/
@Component
@Slf4j
public class Columns implements ColumnApi {

    private final SpringJdbcTemplateContext context;
    private final TableApi tableApi;

    public Columns(SpringJdbcTemplateContext context, TableApi tableApi) {
        this.context = context;
        this.tableApi = tableApi;
    }

    @Override
    public void delete(RequestHeaderObject requestHeaderObject, String table, String column) {
        JdbcTemplate template = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);

        List<String> primaries = this.listPrimary(requestHeaderObject, table);
        if (primaries.contains(column) && primaries.size() == 1) {
            throw new GendbException(String.format("Can not delete column '%s'. it's the only one primary key", column));
        }

        String sql = AlterColumnBuilder.dropColumnSql(model.getSchema(), table, column);
        log.debug(pretty(requestHeaderObject, "Drop column", table, sql));

        template.execute(sql);
    }

    @Override
    public void add(RequestHeaderObject requestHeaderObject, String table, ColumnDefinition definition) {
        if (definition.isPrimaryKey()) {
            Assert.state(definition.isNotNull(), () -> "Primary key can not be null", 400);
            if (definition.isAutoIncrement() && Objects.equals("id", definition.getName())) {
                addAutoIncrementId(requestHeaderObject, table, definition);
                return;
            }
        }

        JdbcTemplate template = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);

        String sql = AlterColumnBuilder.addNormalColumnSql(definition, model.getSchema(), table);
        log.debug(pretty(requestHeaderObject, "Add column", table, sql));
        template.execute(sql);

        postProcessIfPrimaryEnabled(requestHeaderObject, table, definition);

    }

    @Override
    public List<String> listPrimary(RequestHeaderObject requestHeaderObject, String table) {
        TableMetadata metadata = tableApi.tableMetadata(requestHeaderObject, table);
        List<String> primaries = metadata.getColumns()
                .stream()
                .filter(ColumnDefinition::isPrimaryKey)
                .map(ColumnDefinition::getName)
                .collect(Collectors.toList());

        return primaries;
    }

    @Override
    public void setPrimary(RequestHeaderObject requestHeaderObject, String table, List<String> keys) {

        String primaries = keys.stream()
                .map(e -> String.format("`%s`", e))
                .collect(Collectors.joining(","));
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);

        String sql;
        if (keys.size() == 1) {
            sql = String.format("alter table `%s`.`%s` add primary key (%s)", model.getSchema(), table, primaries);
        } else {
            sql = String.format("alter table `%s`.`%s` drop primary key, add primary key (%s)", model.getSchema(), table, primaries);
        }

        JdbcTemplate template = context.getJdbcTemplate(requestHeaderObject);
        log.debug(pretty(requestHeaderObject, "Set PrimaryKey", table, sql));
        template.execute(sql);
    }

    @Override
    public void update(RequestHeaderObject requestHeaderObject, String table, ColumnDefinition definition, String newColumnName) {
        if (definition.isPrimaryKey()) {
            Assert.state(definition.isNotNull(), () -> "Primary key can not be null", 400);
        }
        Assert.state(!definition.isAutoIncrement(), () -> "The auto-incrementing primary key can be specified only when creating the table or add new column");

        JdbcTemplate template = context.getJdbcTemplate(requestHeaderObject);
        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String sql;
        if (StringUtils.hasText(newColumnName)) {
            sql = AlterColumnBuilder.modifyColumnNameSql(definition, model.getSchema(), table, newColumnName);
            log.debug(pretty(requestHeaderObject, "Rename column", table, sql));
        } else {
            sql = AlterColumnBuilder.modifyColumnDefinitionSql(definition, model.getSchema(), table);
            log.debug(pretty(requestHeaderObject, "Modify column", table, sql));
        }

        template.execute(sql);

        postProcessIfPrimaryEnabled(requestHeaderObject, table, definition);
    }

    private void postProcessIfPrimaryEnabled(RequestHeaderObject requestHeaderObject, String table, ColumnDefinition definition) {
        if (definition.isPrimaryKey()) {
            List<String> primaries = this.listPrimary(requestHeaderObject, table);
            if (primaries.contains(definition.getName())) {
                return;
            }
            primaries.add(definition.getName());
            setPrimary(requestHeaderObject, table, primaries);
        }
    }

    private void addAutoIncrementId(RequestHeaderObject requestHeaderObject, String table, ColumnDefinition definition) {

        ExternalDatasourceModel model = extractExternalDatasourceModel(requestHeaderObject);
        String sql = AlterColumnBuilder.addAutoIncrementIdSql(definition, model.getSchema(), table);
        log.debug(pretty(requestHeaderObject, "Add Auto-increment id", table, sql));
        JdbcTemplate template = context.getJdbcTemplate(requestHeaderObject);
        template.execute(sql);

        List<String> primaries = new LinkedList<>();
        primaries.add(definition.getName());

        List<String> existPrimaries = this.listPrimary(requestHeaderObject, table);
        primaries.addAll(existPrimaries);

        setPrimary(requestHeaderObject, table, primaries);

        String setAutoIncrementIdSql = AlterColumnBuilder.setAutoIncrementIdSql(definition, model.getSchema(), table);
        log.debug(pretty(requestHeaderObject, "Set Auto-increment id", table, setAutoIncrementIdSql));
        template.execute(setAutoIncrementIdSql);
    }
}
